package com.jiantong.web.open;

import com.google.common.base.Objects;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
import com.jiantong.common.enums.UserType;
import com.jiantong.common.model.BaseLoginUser;
import com.jiantong.common.util.UserUtil;
import com.jiantong.user.enums.*;
import com.jiantong.user.ext.LoginUser;
import com.jiantong.user.model.*;
import com.jiantong.user.model.AppUserRole;
import com.jiantong.user.service.*;
import com.jiantong.web.constants.Sessions;
import com.jiantong.web.core.util.LoginUserMaker;
import io.terminus.boot.rpc.common.annotation.RpcConsumer;
import io.terminus.common.model.Response;
import io.terminus.common.utils.BeanMapper;
import io.terminus.pampas.openplatform.annotations.OpenParamHandler;
import io.terminus.pampas.openplatform.core.ParamPreHandle;
import io.terminus.pampas.openplatform.exceptions.OPClientException;
import io.terminus.pampas.openplatform.exceptions.OPServerException;
import io.terminus.session.AFSessionManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static io.terminus.common.utils.Arguments.isEmpty;

@OpenParamHandler(patterns = "*", exclusions = {
        "user.register",
        "user.login",
        "user.login2",
        "server.time.get",
        "mobile.code.send",
        "get.mobile.code",
        "session.id.get",
        "user.captcha.get",
        "server.time",
        "get.session.id",
        "get.user.captcher",
        "user.password.forget",
        "user.forget.password",
        "user.auto.login"
})
@Slf4j
public class SessionHandle implements ParamPreHandle {

    @Autowired
    private AFSessionManager sessionManager;
    @RpcConsumer
    private UserReadService<User> userReadService;
    @RpcConsumer
    private OrganizationUserReadService organizationUserReadService;
    @RpcConsumer
    private DepartmentUserReadService departmentUserReadService;
    @RpcConsumer
    private UserProfileReadService userProfileReadService;
    @RpcConsumer
    private GovernmentUserDutyReadService governmentUserDutyReadService;
    @RpcConsumer
    private AppUserRoleReadService appUserRoleReadService;
    @RpcConsumer
    private BreedFactoryReadService breedFactoryReadService;
    @RpcConsumer
    private ButcherFactoryReadService butcherFactoryReadService;
    private LoadingCache<Long, Response<User>> userCache;

    @PostConstruct
    public void buildCache() {
        userCache = CacheBuilder.newBuilder()
                .expireAfterWrite(30, TimeUnit.MINUTES)
                .build(new CacheLoader<Long, Response<User>>() {
                    @Override
                    public Response<User> load(Long userId) throws Exception {
                        return userReadService.findById(userId);
                    }
                });
    }

    /**
     * check params before calling
     *
     * @param params params from http request
     * @throws OPClientException 客户端异常
     */
    @Override
    public void handle(Map<String, Object> params) throws OPClientException {
        if (params.get("sid") == null || isEmpty((String) params.get("sid"))) {
            return;
        }
        String sessionId = params.get("sid").toString();
        Map<String, Object> snapshot = sessionManager.findSessionById(Sessions.TOKEN_PREFIX, sessionId);
        if (snapshot == null || snapshot.size() == 0 || snapshot.get(Sessions.USER_ID) == null) {
            throw new OPClientException(400, "session.id.expired");
        }
        // refresh
        sessionManager.refreshExpireTime(Sessions.TOKEN_PREFIX, sessionId, Sessions.LONG_INACTIVE_INTERVAL);
        Long uid = Long.parseLong(snapshot.get(Sessions.USER_ID).toString());
        Response<User> findUser = userCache.getUnchecked(uid);
        if (!findUser.isSuccess()) {
            log.error("fail to find user by id={},cause:{}",
                    uid, findUser.getError());
            throw new OPServerException(findUser.getError());
        }

        String role = null;
        if (params.get("role") == null) {
            role = null;
        } else {
            role = params.get("role").toString();
        }
        Integer role1 = null;
        if (role == null || role.isEmpty()) {
            role1 = null;
        } else {
            role1 = Integer.parseInt(role);
        }
        LoginUser loginUser = (LoginUser) wrapLoginUser(findUser.getResult(), role1);
        UserUtil.putCurrentUser(loginUser);
    }

    private BaseLoginUser wrapLoginUser(User user, Integer role) {
        BaseLoginUser baseLoginUser = LoginUserMaker.from(user);
        LoginUser loginUser = BeanMapper.map(baseLoginUser, LoginUser.class);

        Long userId = user.getId();
        Integer userType = user.getType();

        if (Objects.equal(userType, UserType.NORMAL.value())) {
            log.info("current is normal user, userId = {}", userId);
            return warpOrganizationUser(loginUser, userId, user, role);
        }
        return loginUser;
    }

    private BaseLoginUser warpOrganizationUser(LoginUser loginUser, Long userId, User user, Integer role) {
        Integer areaLevel = user.getAreaLevel();
        loginUser.setMobile(user.getMobile());
        loginUser.setEmail(user.getEmail());
        loginUser.setStatus(user.getStatus());
        loginUser.setAreaLevel(areaLevel);
        Response<List<AppUserRole>> appUserRoleResponse = appUserRoleReadService.findAppUserRolesByUserId(userId);
        if (!appUserRoleResponse.isSuccess()) {
            log.error("failed to find user app roles by user id = {}, cause : {}", userId, appUserRoleResponse.getError());
            return loginUser;
        }
        List<Integer> appUserRoles = new ArrayList<>();
        for (AppUserRole appUserRole : appUserRoleResponse.getResult()) {
            appUserRoles.add(appUserRole.getAppRole());
        }
        loginUser.setAppUserRoles(appUserRoles);
        Response<OrganizationUser> resp = organizationUserReadService.findOrganizationUserByUserId(userId);
        if (!resp.isSuccess()) {
            log.error("failed to find organization user by user id = {}, cause : {}", userId, resp.getError());
            return loginUser;
        }
        OrganizationUser organizationUser = resp.getResult();

        loginUser.setOrganizationId(organizationUser.getOrganizationId());
        loginUser.setOrganizationName(organizationUser.getOrganizationName());
        loginUser.setOrganizationType(organizationUser.getOrganizationType());
        loginUser.setCorporateCharter(organizationUser.getCorporateCharter());
        Response<UserProfile> resp1 = userProfileReadService.findUserProfileById(userId);
        if (!resp1.isSuccess()) {
            log.error("failed to find user profile by user id = {}, cause : {}", userId, resp1.getError());
            return loginUser;
        }
        UserProfile userProfile = resp1.getResult();
        loginUser.setUserProfile(userProfile);

        List<Long> breedFactoryIds = new ArrayList<>();
        List<Long> butcherFactoryIds = new ArrayList<>();
        List<Long> declareStationIds = new ArrayList<>();
        List<Long> certificateStationIds = new ArrayList<>();
        if (role == null) {
            return loginUser;
        }
        if (role.equals(com.jiantong.user.enums.AppUserRole.PREVENTION_COORDINATOR.value())
                || role.equals(com.jiantong.user.enums.AppUserRole.ASSOCIATION_INSPECTOR.value())) {
            Response<List<GovernmentUserDuty>> dutyResponse = governmentUserDutyReadService.findGovernmentUserDutiesByUserId(userId, role);
            if (!dutyResponse.isSuccess()) {
                log.error("failed to find user duties case = {}", dutyResponse.getError());
                return loginUser;
            }
            for (GovernmentUserDuty governmentUserDuty : dutyResponse.getResult()) {
                if (governmentUserDuty.getFactoryOrPointType() == 3)
                    breedFactoryIds.add(governmentUserDuty.getFactoryOrPointId());
            }
            loginUser.setBreedFactoryIds(breedFactoryIds);
        } else if (role.equals(com.jiantong.user.enums.AppUserRole.VETERINARIAN.value())) {
            Response<List<GovernmentUserDuty>> dutyResponse = governmentUserDutyReadService.findGovernmentUserDutiesByUserId(userId, role);

            Response<List<BreedFactory>> breedResponse = breedFactoryReadService.findByArea(buildAreaMap(userProfile, areaLevel));
            if (!dutyResponse.isSuccess() || !breedResponse.isSuccess()) {
                log.error("failed to find user duties case = {}", dutyResponse.getError());
                return loginUser;
            }
            for (GovernmentUserDuty governmentUserDuty : dutyResponse.getResult()) {
                if (governmentUserDuty.getFactoryOrPointType() == 2) {//申报点
                    declareStationIds.add(governmentUserDuty.getFactoryOrPointId());
                } else if (governmentUserDuty.getFactoryOrPointType() == 1) {//屠宰厂
                    butcherFactoryIds.add(governmentUserDuty.getFactoryOrPointId());
                } else if (governmentUserDuty.getFactoryOrPointType() == 4) { //出证点
                    certificateStationIds.add(governmentUserDuty.getFactoryOrPointId());
                }
            }
            for (BreedFactory breedFactory : breedResponse.getResult()) {
                breedFactoryIds.add(breedFactory.getId());
            }
            loginUser.setCertificateStationIds(certificateStationIds);
            loginUser.setBreedFactoryIds(breedFactoryIds);
            loginUser.setButcherFactoryIds(butcherFactoryIds);
            loginUser.setDeclareStationIds(declareStationIds);
        } else if (role.equals(com.jiantong.user.enums.AppUserRole.UN_SCALE_BREED_FACTORY_COMMON.value())) {
            Response<List<BreedFactory>> breedResponse = breedFactoryReadService.findByArea(buildAreaMap(userProfile, areaLevel));
            if (!breedResponse.isSuccess()) {
                log.error("failed to find user duties case = {}", breedResponse.getError());
                return loginUser;
            }
            for (BreedFactory breedFactory : breedResponse.getResult()) {
                breedFactoryIds.add(breedFactory.getId());
            }
            loginUser.setBreedFactoryIds(breedFactoryIds);
        } else {
            if (areaLevel.equals(UserAreaLevel.CITY.value())) {
                Response<List<BreedFactory>> breedResponse = breedFactoryReadService.findByArea(buildAreaMap(userProfile, areaLevel));
                Response<List<ButcherFactory>> butcherResponse = butcherFactoryReadService.findByArea(buildAreaMap(userProfile, areaLevel));
                if (!breedResponse.isSuccess() || !butcherResponse.isSuccess()) {
                    log.error("failed to find breed factories case = {}", breedResponse.getError(), butcherResponse.getError());
                    return loginUser;
                }
                for (BreedFactory breedFactory : breedResponse.getResult()) {
                    breedFactoryIds.add(breedFactory.getId());
                }
                for (ButcherFactory butcherFactory : butcherResponse.getResult()) {
                    butcherFactoryIds.add(butcherFactory.getId());
                }
                loginUser.setBreedFactoryIds(breedFactoryIds);
                loginUser.setButcherFactoryIds(butcherFactoryIds);
            } else if (areaLevel.equals(UserAreaLevel.REGION.value())) {
                Response<List<BreedFactory>> breedResponse = breedFactoryReadService.findByArea(buildAreaMap(userProfile, areaLevel));
                Response<List<ButcherFactory>> butcherResponse = butcherFactoryReadService.findByArea(buildAreaMap(userProfile, areaLevel));
                if (!breedResponse.isSuccess() || !butcherResponse.isSuccess()) {
                    log.error("failed to find breed factories case = {}", breedResponse.getError(), butcherResponse.getError());
                    return loginUser;
                }
                for (BreedFactory breedFactory : breedResponse.getResult()) {
                    breedFactoryIds.add(breedFactory.getId());
                }
                for (ButcherFactory butcherFactory : butcherResponse.getResult()) {
                    butcherFactoryIds.add(butcherFactory.getId());
                }
                loginUser.setBreedFactoryIds(breedFactoryIds);
                loginUser.setButcherFactoryIds(butcherFactoryIds);
            }
        }

        return loginUser;
    }

    private Map<String, Object> buildAreaMap(UserProfile userProfile, Integer areaLevel) {
        Map<String, Object> map = Maps.newHashMap();
        if (areaLevel.equals(UserAreaLevel.CITY.value())) {
            map.put("provinceId", userProfile.getProvinceId());
            map.put("cityId", userProfile.getCityId());
        } else if (areaLevel.equals(UserAreaLevel.REGION.value())) {
            map.put("provinceId", userProfile.getProvinceId());
            map.put("cityId", userProfile.getCityId());
            map.put("regionId", userProfile.getRegionId());
        } else if (areaLevel.equals(UserAreaLevel.STREET.value())) {
            map.put("provinceId", userProfile.getProvinceId());
            map.put("cityId", userProfile.getCityId());
            map.put("regionId", userProfile.getRegionId());
            map.put("streetId", userProfile.getStreetId());
        }
        return map;
    }
}
